﻿// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. 
// See License.txt in the project root for license information.

//#define SLEEP_TO_PROVOKE_RACES

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Actors;
using System.Threading.Actors.Utilities;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace TestActorMethods
{
    public class CustomType
    {
        public int PropertyA {get; set;}
        public double PropertyB { get; set; }
        public long PropertyC {get; set;}
        public string PropertyD {get; set;}
    }

    [TestClass]
    public class AsyncMethodCalls
    {
        
        // Try to test some async method call functionality
        [TestMethod]
        public void AsyncMethodCall_Test()
        {
            var state1 = new StandaloneActorState("actor1");
            var state2 = new StandaloneActorState("actor2");

            // Add some actor methods to state1
            state1.Set("Method1", (Func<IActorState, object[], object>)delegate(IActorState state, object[] args)
            {
                return 42;
            });
            state1.Set("Method2", (Func<IActorState, object[], object>)delegate(IActorState state, object[] args)
            {
                state.Set("Foo", 100);
                return "Forty-Two";
            });
            state1.Set("Method3", (Func<IActorState, object[], object>)delegate(IActorState state, object[] args)
            {
                return (long)42;
            });
            state1.Set("Method4", (Func<IActorState, object[], object>)delegate(IActorState state, object[] args)
            {
                return new CustomType() { PropertyA = 1, PropertyB = 2.0, PropertyC = 3L, PropertyD = "Four"}; 
            });

            // Now add some calling methods to state2
            state2.Set("SyncCaller", (Func<IActorState, object[], object>)delegate(IActorState state, object[] args)
            {
                var proxy = state.GetActorProxy("actor1");
                return proxy.Request("Method1", new object[0]).GetTypedResult<int>();
            });
            state2.Set("AsyncCaller", (Func<IActorState, object[], object>)delegate(IActorState state, object[] args)
            {
                var proxy = state.GetActorProxy("actor1");
                var promise = proxy.Request("Method1", new object[0]);
                promise.ContinueWith("ResultHandler");
                return promise;
            });
            state2.Set("ResultHandler", (Func<IActorState, object[], object>)delegate(IActorState state, object[] args)
            {
#if SLEEP_TO_PROVOKE_RACES
                Thread.Sleep(1000);  // This is called as a continuation - ensure we take a long time in testing!
#endif
                var result = args[0];
                state.Set("AsyncResult", result);
                return null;
            });

            state2.Set("IntResultHandler", (Func<IActorState, object[], object>)delegate(IActorState state, object[] args)
            {
#if SLEEP_TO_PROVOKE_RACES
                Thread.Sleep(500);  // This is called as a continuation - ensure we take a long time in testing!
#endif
                var promise = (IActorPromise)args[0];
                var result = promise.GetTypedResult<int>();
                //var result = promise.Result.ToInt(); // Extension method found in Utilities.UtilityMethods.
                state.Set("IntResult", result);
                return null;
            });
            state2.Set("IntResultHandlerD", (Func<dynamic, dynamic[], object>)delegate(dynamic state, dynamic[] args)
            {
#if SLEEP_TO_PROVOKE_RACES
                Thread.Sleep(500);  // This is called as a continuation - ensure we take a long time in testing!
#endif
                var promise = args[0];
                // Alternatives:
                //      var result = (int)(long) promise.Result;
                var result = promise.GetTypedResult<int>();
                state.IntResultD = result;
                return null;
            });


            state2.Set("FNFCaller", (Func<IActorState, object[], object>)delegate(IActorState state, object[] args)
            {
                var proxy = state.GetActorProxy("actor1");
                proxy.Command("Method2", new object[0]);
                return null;
            });

            object temp;
            Assert.IsFalse(state1.TryGet("Foo", out temp), "Expected state1.Foo to be unset before beginning");
            Assert.IsFalse(state2.TryGet("AsyncResult", out temp), "Expected state2.AsyncResult to be unset before beginning");
            temp = state2.CallMethod("SyncCaller", new object[0]);
            Assert.AreEqual((int)temp, 42, "Expected sync call to return 42");
            var promiseAsync = (IActorPromise) state2.CallMethod("AsyncCaller", new object[0]);
            bool timedOut = !promiseAsync.Wait(5000);
            Assert.IsFalse(timedOut, "Calling an actor method that asynchronously calls another actor method then waiting for 5 seconds timed out!");
            timedOut = !state2.WaitForKey("AsyncResult", 1000); // The continuation may take a while to complete after the promise completes
            Assert.IsTrue(!timedOut, "Expected \"AsyncResult\" to be set after \"AsyncCaller\" called and waiting on the promise returned true!");
            state2.CallMethod("FNFCaller", new object[0]);
            Assert.IsTrue(state1.TryGet("Foo", out temp), "Expected Foo to be set in state1 after FNFCaller");


            // test int<->long conversion issues...
            dynamic dstate2 = new DynamicActorStateWrapper(state2);
            var sresult = dstate2.GetActorProxy("actor1").Method3().Result;
            int i = dstate2.GetActorProxy("actor1").Method3().GetTypedResult<int>();
            long l = dstate2.GetActorProxy("actor1").Method3().GetTypedResult<long>();
            string s = dstate2.GetActorProxy("actor1").Method2().GetTypedResult<string>();

            // test int result and continuation w/ static state
            var dynamicActorPromise = dstate2.GetActorProxy("actor1").Method3();
            dynamicActorPromise.ContinueWith("IntResultHandler");
            dynamicActorPromise.Wait();
            Assert.IsTrue(state2.WaitForKey("IntResult", 1000), "Expected state2.IntResult to be populated within 1000 ms of promise completing");
            Assert.IsTrue((int)state2.Get("IntResult") == 42, "Expected state2.IntResult to be 42 after IntResultHandler and waiting");

            // test int result and continuation w/ dynamic state
            dynamicActorPromise = dstate2.GetActorProxy("actor1").Method3();
            dynamicActorPromise.ContinueWith("IntResultHandlerD");
            dynamicActorPromise.Wait(5000);
            Assert.IsTrue(state2.WaitForKey("IntResultD", 1000), "Expected state2.IntResultD to be populated within 1000 ms of promise completing");
            Assert.IsTrue((int)state2.Get("IntResultD") == 42, "Expected state2.IntResultD to be 42 after IntResultHandlerD and waiting");

            // test that exceptions are properly noted in returned promise
            var promise2 = dstate2.GetActorProxy("actor1").NonExistentMethod();
            // We've added code to IsFaulted so it should block until the method completes.
            Assert.IsTrue(promise2.IsFaulted, "Expected faulted promise after call to non-existent method");

        }
    }
}
